﻿//------------------------------------------------------------------------------
// <copyright file="LoopbackServer.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
// The loopback server that listens for client connections.
// </summary>
//------------------------------------------------------------------------------

namespace Microsoft.Internal.GamesTest.Rpc.Tests.RemoteLoopback
{
    // Standard namespaces...
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;

    // User-defined namespaces...
    using Microsoft.Internal.GamesTest.Rpc.Tests.Utilities;

    /// <summary>
    /// The loopback server that listens for client connections.
    /// </summary>
    public class LoopbackServer
    {
        /// <summary>
        /// An <c>object</c> to lock multi-threaded access to the active connection collection.
        /// </summary>
        private readonly object locker = new object();

        /// <summary>
        /// Initializes a new instance of the <see cref="LoopbackServer" /> class.
        /// </summary>
        /// <param name="startThread">A value indicating whether the listener should be started in a thread.</param>
        public LoopbackServer(bool startThread)
        {
            // Initialize...
            this.ActiveConnections = new List<LoopbackConnection>();
            this.Port = Settings.Default.InboundPort;
            this.Listener = new TcpListener(IPAddress.Any, this.Port);

            // Start the listener thread...
            if (startThread)
            {
                this.ListenThread = new Thread(new ThreadStart(this.ListenForClients));
                this.ListenThread.Start();
            }
        }

        /// <summary>
        /// Gets the list of active connections.
        /// </summary>
        /// <value>The list of active connections.</value>
        public List<LoopbackConnection> ActiveConnections { get; private set; }

        /// <summary>
        /// Gets an <c>object</c> to lock multi-threaded access to the active connection collection.
        /// </summary>
        /// <value>An <c>object</c> to lock multi-threaded access to the active connection collection.</value>
        public object ActiveConnectionsLock
        {
            get { return this.locker; }
        }

        /// <summary>
        /// Gets the TCP listener for the server.
        /// </summary>
        /// <value>The TCP listener for the server.</value>
        public TcpListener Listener { get; private set; }

        /// <summary>
        /// Gets the thread that listens for client connections.
        /// </summary>
        /// <value>The thread that listens for client connections.</value>
        public Thread ListenThread { get; private set; }

        /// <summary>
        /// Gets the next available connection ID.
        /// </summary>
        /// <value>The next available connection ID.</value>
        public uint NextConnectionId { get; private set; }

        /// <summary>
        /// Gets the inbound port number to listen for client connection on.
        /// </summary>
        /// <value>The inbound port number to listen for client connection on.</value>
        public int Port { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether the listen thread should continue accepting new connections.
        /// </summary>
        /// <value>A value indicating whether the listen thread should continue accepting new connections.</value>
        public bool Run { get; set; }

        /// <summary>
        /// Gets the total number of connections serviced.
        /// </summary>
        /// <value>The total number of connections serviced.</value>
        public int TotalConnections { get; private set; }

        /// <summary>
        /// Gets or sets a value indicating whether server is closing connections.
        /// </summary>
        /// <value>A value indicating whether server is closing connections.</value>
        private bool ClosingConnections { get; set; }

        /// <summary>
        /// Closes all active connections.
        /// </summary>
        public void CloseAllActiveConnections()
        {
            // Close all the connections...
            this.ClosingConnections = true;
            Console.WriteLine("Closing all active connections...");
            foreach (LoopbackConnection connection in this.ActiveConnections)
            {
                connection.InboundClient.Close();
                connection.OutboundClient.Close();
            }

            // Make sure all threads have terminated...
            foreach (LoopbackConnection connection in this.ActiveConnections)
            {
                if (!connection.InboundThread.Join(Settings.Default.CloseConnectionTimeout))
                {
                    Console.WriteLine("Close Connections: The inbound connection to {0} did not close in a timely manner.  Aborting the thread...", connection.InboundClient);
                    connection.InboundThread.Abort();
                }

                if (!connection.OutboundThread.Join(Settings.Default.CloseConnectionTimeout))
                {
                    Console.WriteLine("Close Connections: The outbound connection to {0} did not close in a timely manner.  Aborting the thread...", connection.OutboundClient);
                    connection.OutboundThread.Abort();
                }
            }

            // Clear out the list...
            Console.WriteLine("{0} connections closed.", this.ActiveConnections.Count);
            this.ActiveConnections.Clear();
            this.ClosingConnections = false;
        }

        /// <summary>
        /// The thread that listens for client connections.
        /// </summary>
        public void ListenForClients()
        {
            // Start the listener...
            Console.WriteLine("Starting remote loopback server on port {0}...", this.Port);
            this.Listener.Start();
            this.Run = true;

            while (this.Run)
            {
                // Wait for a client connection...
                if (!this.Listener.Pending())
                {
                    Thread.Sleep(Settings.Default.PendingConnectionCheckInterval);
                    continue;
                }

                // Connect to the pending client...
                TcpClient client = this.Listener.AcceptTcpClient();

                // Create a connection to handle the client communication...
                try
                {
                    LoopbackConnection connection = new LoopbackConnection(client, this, this.NextConnectionId++);

                    // Check to see if there is a lingering connection from the host in the list.
                    // If there is a lingering connection this closes the connection and accepts
                    // the new incoming connection.
                    for (int i = 0; i < this.ActiveConnections.Count; i++)
                    {
                        lock (this.ActiveConnectionsLock)
                        {
                            if (this.ActiveConnections[i].InboundClient.IPAddress.Equals(connection.InboundClient.IPAddress) &&
                                (DateTime.Now - this.ActiveConnections[i].EstablishedTime).TotalHours >= 1.0)
                            {
                                Console.WriteLine("Removing old connection to same host (> 1 hour) {0}...", connection.InboundClient);

                                this.ActiveConnections[i].InboundClient.Close();
                                this.ActiveConnections[i].OutboundClient.Close();
                                this.RemoveConnection(this.ActiveConnections[i]);
                            }
                        }
                    }

                    if (connection.OutboundClient != null)
                    {
                        Console.WriteLine("Adding new connection...");
                        this.ActiveConnections.Add(connection);
                        this.TotalConnections++;
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("An unexpected error occurred while trying to establish the relay connection {0}", e.ToString());
                }

                Console.WriteLine("Total Connections: {0}, Total Active: {1}", this.TotalConnections, this.ActiveConnections.Count);
            }

            // Done...
            this.Listener.Stop();
            Console.WriteLine("The remote loopback server has stopped ({0} connections serviced).", this.TotalConnections);
        }

        /// <summary>
        /// Removes a connection from the active connection list.
        /// </summary>
        /// <param name="item">The connection to remove.</param>
        public void RemoveConnection(LoopbackConnection item)
        {
            if (!this.ClosingConnections)
            {
                Console.WriteLine("Removing connection...");
                this.ActiveConnections.Remove(item);
                Console.WriteLine("Total Connections: {0}, Total Active: {1}", this.TotalConnections, this.ActiveConnections.Count);
            }
        }
    }
}
